/*
 * @(#)SimpleStyleTreeCellRenderer.java
 *
 * Copyright 2002 - 2005 JIDE Software. All rights reserved.
 */

package com.element.ui.tree;

import com.element.plaf.UIDefaultsLookup;
import com.element.ui.label.StyledLabel;

import javax.swing.*;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import javax.swing.tree.TreeCellRenderer;
import java.awt.*;

/**
 * A tree cell renderer based on StyledLabel. To use it, you should make your cell renderer extending this one and
 * override {@link #customizeStyledLabel(JTree, Object, boolean, boolean, boolean, int, boolean)} method. If your
 * overridden method, you can call setStyleRange() or setStyleRanges() based on the tree node value and row index.
 */
public class StyledTreeCellRenderer extends StyledLabel implements TreeCellRenderer {
	/**
	 * Last tree the renderer was painted in.
	 */
	private JTree tree;

	/**
	 * Is the value currently selected.
	 */
	protected boolean selected;
	/**
	 * True if has focus.
	 */
	protected boolean hasFocus;
	/**
	 * True if draws focus border around icon as well.
	 */
	private boolean drawsFocusBorderAroundIcon;
	/**
	 * If true, a dashed line is drawn as the focus indicator.
	 */
	private boolean drawDashedFocusIndicator;

	// If drawDashedFocusIndicator is true, the following are used.
	/**
	 * Background color of the tree.
	 */
	private Color treeBGColor;
	/**
	 * Color to draw the focus indicator in, determined from the background. color.
	 */
	private Color focusBGColor;

	// Icons
	/**
	 * Icon used to show non-leaf nodes that aren't expanded.
	 */
	transient protected Icon closedIcon;

	/**
	 * Icon used to show leaf nodes.
	 */
	transient protected Icon leafIcon;

	/**
	 * Icon used to show non-leaf nodes that are expanded.
	 */
	transient protected Icon openIcon;

	// Colors
	/**
	 * Color to use for the foreground for selected nodes.
	 */
	protected Color textSelectionColor;

	/**
	 * Color to use for the foreground for non-selected nodes.
	 */
	protected Color textNonSelectionColor;

	/**
	 * Color to use for the background when a node is selected.
	 */
	protected Color backgroundSelectionColor;

	/**
	 * Color to use for the background when the node isn't selected.
	 */
	protected Color backgroundNonSelectionColor;

	/**
	 * Color to use for the focus indicator when the node has focus.
	 */
	protected Color borderSelectionColor;

	/**
	 * Returns a new instance of DefaultTreeCellRenderer.  Alignment is set to left aligned. Icons and text color are
	 * determined from the UIManager.
	 */
	public StyledTreeCellRenderer() {
		updateUIDefaults();
	}

	@Override
	public void updateUI() {
		super.updateUI();
		updateUIDefaults();
	}

	private void updateUIDefaults() {
		setLeafIcon(UIDefaultsLookup.getIcon("Tree.leafIcon"));
		setClosedIcon(UIDefaultsLookup.getIcon("Tree.closedIcon"));
		setOpenIcon(UIDefaultsLookup.getIcon("Tree.openIcon"));

		setTextSelectionColor(UIDefaultsLookup.getColor("Tree.selectionForeground"));
		setTextNonSelectionColor(UIDefaultsLookup.getColor("Tree.textForeground"));
		setBackgroundSelectionColor(UIDefaultsLookup.getColor("Tree.selectionBackground"));
		setBackgroundNonSelectionColor(UIDefaultsLookup.getColor("Tree.textBackground"));
		setBorderSelectionColor(UIDefaultsLookup.getColor("Tree.selectionBorderColor"));
		Object value = UIDefaultsLookup.get("Tree.drawsFocusBorderAroundIcon");
		drawsFocusBorderAroundIcon = (value != null && (Boolean) value);
		value = UIDefaultsLookup.get("Tree.drawDashedFocusIndicator");
		drawDashedFocusIndicator = (value != null && (Boolean) value);
	}


	/**
	 * Returns the default icon, for the current laf, that is used to represent non-leaf nodes that are expanded.
	 */
	public Icon getDefaultOpenIcon() {
		return UIDefaultsLookup.getIcon("Tree.openIcon");
	}

	/**
	 * Returns the default icon, for the current laf, that is used to represent non-leaf nodes that are not expanded.
	 */
	public Icon getDefaultClosedIcon() {
		return UIDefaultsLookup.getIcon("Tree.closedIcon");
	}

	/**
	 * Returns the default icon, for the current laf, that is used to represent leaf nodes.
	 */
	public Icon getDefaultLeafIcon() {
		return UIDefaultsLookup.getIcon("Tree.leafIcon");
	}

	/**
	 * Sets the icon used to represent non-leaf nodes that are expanded.
	 */
	public void setOpenIcon(Icon newIcon) {
		openIcon = newIcon;
	}

	/**
	 * Returns the icon used to represent non-leaf nodes that are expanded.
	 */
	public Icon getOpenIcon() {
		return openIcon;
	}

	/**
	 * Sets the icon used to represent non-leaf nodes that are not expanded.
	 */
	public void setClosedIcon(Icon newIcon) {
		closedIcon = newIcon;
	}

	/**
	 * Returns the icon used to represent non-leaf nodes that are not expanded.
	 */
	public Icon getClosedIcon() {
		return closedIcon;
	}

	/**
	 * Sets the icon used to represent leaf nodes.
	 */
	public void setLeafIcon(Icon newIcon) {
		leafIcon = newIcon;
	}

	/**
	 * Returns the icon used to represent leaf nodes.
	 */
	public Icon getLeafIcon() {
		return leafIcon;
	}

	/**
	 * Sets the color the text is drawn with when the node is selected.
	 */
	public void setTextSelectionColor(Color newColor) {
		textSelectionColor = newColor;
	}

	/**
	 * Returns the color the text is drawn with when the node is selected.
	 */
	public Color getTextSelectionColor() {
		return textSelectionColor;
	}

	/**
	 * Sets the color the text is drawn with when the node isn't selected.
	 */
	public void setTextNonSelectionColor(Color newColor) {
		textNonSelectionColor = newColor;
	}

	/**
	 * Returns the color the text is drawn with when the node isn't selected.
	 */
	public Color getTextNonSelectionColor() {
		return textNonSelectionColor;
	}

	/**
	 * Sets the color to use for the background if node is selected.
	 */
	public void setBackgroundSelectionColor(Color newColor) {
		backgroundSelectionColor = newColor;
	}


	/**
	 * Returns the color to use for the background if node is selected.
	 */
	public Color getBackgroundSelectionColor() {
		return backgroundSelectionColor;
	}

	/**
	 * Sets the background color to be used for non selected nodes.
	 */
	public void setBackgroundNonSelectionColor(Color newColor) {
		backgroundNonSelectionColor = newColor;
	}

	/**
	 * Returns the background color to be used for non selected nodes.
	 */
	public Color getBackgroundNonSelectionColor() {
		return backgroundNonSelectionColor;
	}

	/**
	 * Sets the color to use for the border.
	 */
	public void setBorderSelectionColor(Color newColor) {
		borderSelectionColor = newColor;
	}

	/**
	 * Returns the color the border is drawn.
	 */
	public Color getBorderSelectionColor() {
		return borderSelectionColor;
	}

	/**
	 * Subclassed to map <code>FontUIResource</code>s to null. If <code>font</code> is null, or a
	 * <code>FontUIResource</code>, this has the effect of letting the font of the JTree show through. On the other
	 * hand, if <code>font</code> is non-null, and not a <code>FontUIResource</code>, the font becomes
	 * <code>font</code>.
	 */
	@Override
	public void setFont(Font font) {
		if (font instanceof FontUIResource)
			font = null;
		super.setFont(font);
	}

	/**
	 * Gets the font of this component.
	 *
	 * @return this component's font; if a font has not been set for this component, the font of its parent is returned
	 */
	@Override
	public Font getFont() {
		Font font = super.getFont();

		if (font == null && tree != null) {
			// Strive to return a non-null value, otherwise the html support
			// will typically pick up the wrong font in certain situations.
			font = tree.getFont();
		}
		return font;
	}

	/**
	 * Subclassed to map <code>ColorUIResource</code>s to null. If <code>color</code> is null, or a
	 * <code>ColorUIResource</code>, this has the effect of letting the background color of the JTree show through. On
	 * the other hand, if <code>color</code> is non-null, and not a <code>ColorUIResource</code>, the background becomes
	 * <code>color</code>.
	 */
	@Override
	public void setBackground(Color color) {
		if (color instanceof ColorUIResource)
			color = null;
		super.setBackground(color);
	}

	/**
	 * Configures the renderer based on the passed in components. The value is set from messaging the tree with
	 * <code>convertValueToText</code>, which ultimately invokes <code>toString</code> on <code>value</code>. The
	 * foreground color is set based on the selection and the icon is set based on the <code>leaf</code> and
	 * <code>expanded</code> parameters.
	 */
	public Component getTreeCellRendererComponent(JTree tree, Object value,
	                                              boolean sel,
	                                              boolean expanded,
	                                              boolean leaf, int row,
	                                              boolean hasFocus) {
		setOpaque(false);
		// There needs to be a way to specify disabled icons.
		if (!tree.isEnabled()) {
			setEnabled(false);
			if (leaf) {
				setDisabledIcon(getLeafIcon());
			} else if (expanded) {
				setDisabledIcon(getOpenIcon());
			} else {
				setDisabledIcon(getClosedIcon());
			}
		} else {
			setEnabled(true);
			if (leaf) {
				setIcon(getLeafIcon());
			} else if (expanded) {
				setIcon(getOpenIcon());
			} else {
				setIcon(getClosedIcon());
			}
		}

		setIgnoreColorSettings(sel);
		customizeStyledLabel(tree, value, sel, expanded, leaf, row, hasFocus);

		this.tree = tree;
		this.hasFocus = hasFocus;
		if (sel)
			setForeground(getTextSelectionColor());
		else
			setForeground(getTextNonSelectionColor());

		applyComponentOrientation(tree.getComponentOrientation());

		selected = sel;

		return this;
	}

	/**
	 * Overrides this method to customize the styled label.
	 *
	 * @param tree
	 * @param value
	 * @param sel
	 * @param expanded
	 * @param leaf
	 * @param row
	 * @param hasFocus
	 */
	protected void customizeStyledLabel(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
		String stringValue = tree.convertValueToText(value, sel,
				expanded, leaf, row, hasFocus);
		clearStyleRanges();
		setText(stringValue);
	}

	/**
	 * Paints the value.  The background is filled based on selected.
	 */
	@Override
	public void paint(Graphics g) {
		Color bColor;

		if (selected) {
			bColor = getBackgroundSelectionColor();
		} else {
			bColor = getBackgroundNonSelectionColor();
			if (bColor == null)
				bColor = getBackground();
		}
		int imageOffset = -1;

		if (selected || isOpaque()) {
			if (bColor != null) {
				imageOffset = getLabelStart();
				g.setColor(bColor);
				if (getComponentOrientation().isLeftToRight()) {
					g.fillRect(imageOffset, 0, getWidth() - imageOffset, getHeight());
				} else {
					g.fillRect(0, 0, getWidth() - imageOffset, getHeight());
				}
			}
		}

		super.paint(g);

		if (hasFocus) {
			if (drawsFocusBorderAroundIcon) {
				imageOffset = 0;
			} else if (imageOffset == -1) {
				imageOffset = getLabelStart();
			}
			if (getComponentOrientation().isLeftToRight()) {
				paintFocus(g, imageOffset, 0, getWidth() - imageOffset,
						getHeight());
			} else {
				paintFocus(g, 0, 0, getWidth() - imageOffset, getHeight());
			}
		}
	}

	private void paintFocus(Graphics g, int x, int y, int w, int h) {
		Color bsColor = getBorderSelectionColor();

		if (bsColor != null && (selected || !drawDashedFocusIndicator)) {
			g.setColor(bsColor);
			g.drawRect(x, y, w - 1, h - 1);
		}
		if (drawDashedFocusIndicator) {
			Color color;
			if (selected) {
				color = getBackgroundSelectionColor();
			} else {
				color = getBackgroundNonSelectionColor();
				if (color == null) {
					color = getBackground();
				}
			}

			if (treeBGColor != color) {
				treeBGColor = color;
				focusBGColor = new Color(~color.getRGB());
			}
			g.setColor(focusBGColor);
			BasicGraphicsUtils.drawDashedRect(g, x, y, w, h);
		}
	}

	private int getLabelStart() {
		Icon icon = getIcon();
		if (icon != null && getText().trim().length() != 0) {
			return icon.getIconWidth() + Math.max(0, getIconTextGap());
		}
		return 0;
	}

	/**
	 * Overrides <code>JComponent.getPreferredSize</code> to return slightly wider preferred size value.
	 */
	@Override
	public Dimension getPreferredSize() {
		Dimension retDimension = super.getPreferredSize();

		if (retDimension != null)
			retDimension = new Dimension(retDimension.width + 3,
					retDimension.height);
		return retDimension;
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void validate() {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 *
	 * @since 1.5
	 */
	@Override
	public void invalidate() {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void revalidate() {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void repaint(long tm, int x, int y, int width, int height) {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void repaint(Rectangle r) {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 *
	 * @since 1.5
	 */
	@Override
	public void repaint() {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
		// Strings get interned...
		if (propertyName.equals("text"))
			super.firePropertyChange(propertyName, oldValue, newValue);
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void firePropertyChange(String propertyName, byte oldValue, byte newValue) {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void firePropertyChange(String propertyName, char oldValue, char newValue) {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void firePropertyChange(String propertyName, short oldValue, short newValue) {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void firePropertyChange(String propertyName, int oldValue, int newValue) {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void firePropertyChange(String propertyName, long oldValue, long newValue) {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void firePropertyChange(String propertyName, float oldValue, float newValue) {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void firePropertyChange(String propertyName, double oldValue, double newValue) {
	}

	/**
	 * Overridden for performance reasons. See the <a href="#override">Implementation Note</a> for more information.
	 */
	@Override
	public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
	}

}
